package main

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

func main() {
	work1()
	work2()
	work3()
}

func work1() {
	// 题1：看下面一段程序，回答问题
	// s1 := make([]int, 3, 4)
	// s2 := append(s1, 1)
	// 请问s1、s2内各有什么元素？
	// s1修改一个元素会影响s2吗？s2修改一个元素会影响s1吗？
	// s2再增加一个元素会怎么样？

	// 使用make创建一个长度为3，容量为4的切片s1
	// 因为s1定义是没有手动赋初始值，go使用零值，所以此时s1为：[0 0 0]
	s1 := make([]int, 3, 4)
	fmt.Printf("s1: %p,%[1]d\n", s1)

	// s1使用append在切片尾部追加元素“1”，因为增加了1个元素，没有超过s1现有底层数组的cap值4，所以不会引起底层数扩容
	// 所以通过对比s1,s2的底层数组的内存地址，发现为同一个底层数组
	// append尾部追加元素“1”，此时s2内元素为：[0 0 0 1]
	s2 := append(s1, 1)
	fmt.Printf("s2: %p,%[1]d\n", s2)
	fmt.Println()

	// 因为s1,s2为同一个底层数组，所以修改s1元素的值，s2的值也会相应变更
	s1[0] = 100
	fmt.Printf("s1: %p,%[1]d\n", s1)
	fmt.Printf("s2: %p,%[1]d\n", s2)

	// 同样修改s2元素的值，s1的值也会同步变更
	s2[1] = 200
	fmt.Printf("s1: %p,%[1]d\n", s1)
	fmt.Printf("s2: %p,%[1]d\n", s2)
	fmt.Println()

	// 再追加1个元素后，超过了切片的最大容量，所以底层数组会扩容
	// 此时在查看s2底层数组的内存地址，发现和之前不一样了。
	s2 = append(s2, 2)
	fmt.Printf("s2: %p,%[1]d\n", s2)

	fmt.Println("------------------------------------")
	fmt.Println()
}

// 题2：有一个数组 [1,4,9,16,2,5,10,15]，生成一个新切片，
//
//	要求新切片元素是数组相邻2项的和。
func work2() {
	a1 := [...]int{1, 4, 9, 16, 2, 5, 10, 15}
	// 使用make构造一个容量等于a1长度减1的切片
	s1 := make([]int, 0, len(a1)-1)

	// 使用for循环,通过a1的索引获取相邻2项和的值，并append到s1
	for i := 0; i < len(a1)-1; i++ {
		s1 = append(s1, a1[i]+a1[i+1])
		// fmt.Println(s1)
	}
	fmt.Println(s1)
	fmt.Println("------------------------------------")
}

// 题3:数字重复统计
// 随机产生100个整数
// 数字的范围[-100, 100]
// 降序输出这些生成的数字并打印其重复的次数

func work3() {
	// 使用系统时间设置随机数种子
	src := rand.NewSource(time.Now().UnixNano())
	// 创建随机数生成器
	r := rand.New(src)

	// 定义一个切片存放生成的随机数
	numSlice := make([]int, 0, 100)
	// 使用for循环控制生成随机数的个数
	for i := 0; i < 100; i++ {
		// 使用随机数生成器生成访问为[-100,100]的随机数，并追加到切片中
		numSlice = append(numSlice, r.Intn(201)-100)
	}

	// 定义一个map，统计数字重复的次数
	countMap := make(map[int]int)
	for _, num := range numSlice {
		// 将 num 作为键，在 countMap 中找到相应的值，并将该值增加1
		countMap[num]++
	}

	// 定义一个切片，存放countMap map中的key，目的是后续输出结果的去重和排序。
	numSlice2 := make([]int, 0, 100)
	// 遍历countMap map的所有key,追加到切片numSlice2中，主要是实现key去重的目的
	for k, _ := range countMap {
		numSlice2 = append(numSlice2, k)
	}

	// 降序排序
	sort.Sort(sort.Reverse(sort.IntSlice(numSlice2)))

	// 输出各随机数重复的次数
	for _, num := range numSlice2 {
		fmt.Printf("%d 出现的次数：%d\n", num, countMap[num])
	}
}

// 题4：简述线性数据结构和哈希表优劣

/*
线性数据结构：
优点：
l.简单易懂：线性数据结构如数组、切片等比较简单直观，易于理解和实现。
2.可使用索引顺序访问：线性数据结构支持按照索引顺序访问元素，对于按照顺序存储的数据具有较好的性能。
缺点：
1.插入和删除操作效率低：在某些情况下，插入和删除操作可能需要移动大量元素（引起底层数组的扩缩容），
  特别是在数组中间插入或删除元素时。
2.按内容查找效率低：线性数据结构的查找效率取决于元素的数量，如果元素数量庞大，线性搜索的效率可能较低。

----------------
哈希表：
优点：
1.快速的查找和插入操作：哈希表通过哈希函数将键映射到内存中的位置，从而实现常量时间的查找和插入操作，通常情况下具有很高的效率。
2.适用于大规模数据：在大规模数据集中，哈希表的查找和插入操作仍然能够保持较高的性能。
缺点：
1.空间开销较大：由于哈希表需要维护哈希函数和冲突处理等额外信息，可能会占用较多的内存空间。
2.不支持顺序访问：哈希表中元素的排列是无序的，无法像数组和链表那样支持顺序访问。
3.哈希冲突：不同的键可能会映射到相同的位置，导致哈希冲突，需要额外的处理方法来解决。

因此，在选择数据结构时，需要根据具体的应用场景来决定使用哪种数据结构。
如果需要快速的查找和插入操作，并且数据量较大，可以考虑使用哈希表；
如果数据量较小或者需要支持顺序访问，线性数据结构可能更适合。
*/
